package com.gitee.ly;

import com.gitee.ly.json.Json;
import com.gitee.ly.model.*;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.jetbrains.annotations.NotNull;
import retrofit2.Retrofit;
import retrofit2.converter.jackson.JacksonConverterFactory;

import java.io.IOException;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Slf4j
public class ClientManage {
    private static final long reenTranLockTimeOutSeconds = 60;
    private boolean empty = false;
    //以/结尾
    private String serverBaseUrl;
    private String serviceName;
    private String schema;
    private String ip;
    private Integer port;
    private String headerU;
    private ServerApi serverApi;
    private final ScheduledThreadPoolExecutor serviceAutoRegistryScheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(1);
    private final Map<String, List<ServiceInfo>> serviceInfoCacheMap = new ConcurrentHashMap<>();
    private final ScheduledThreadPoolExecutor serviceInfoCacheRefreshScheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(1);

    public ClientManage(String serverBaseUrl, String serviceName, String schema, String ip, int port, String headerU) {
        this(serverBaseUrl, serviceName, schema, ip, port, headerU, false);
    }

    public ClientManage(String serverBaseUrl, String serviceName, String schema, String ip, int port, String headerU, boolean useEmpty) {
        OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder().callTimeout(Duration.ofSeconds(10)).connectTimeout(Duration.ofSeconds(10)).readTimeout(Duration.ofSeconds(10)).writeTimeout(Duration.ofSeconds(10));
        okHttpClientBuilder.addInterceptor(new Interceptor() {
            @NotNull
            @Override
            public Response intercept(@NotNull Chain chain) throws IOException {
                Request.Builder builder = chain.request().newBuilder();
                builder.addHeader("u", headerU);
                return chain.proceed(builder.build());
            }
        });
        OkHttpClient okHttpClient = okHttpClientBuilder.build();
        Retrofit.Builder retrofitBuilder = new Retrofit.Builder().baseUrl(serverBaseUrl).client(okHttpClient);
        retrofitBuilder.addConverterFactory(JacksonConverterFactory.create(Json.OBJECT_MAPPER));
        this.serverBaseUrl = serverBaseUrl;
        this.serviceName = serviceName;
        this.schema = schema;
        this.ip = ip;
        this.port = port;
        this.headerU = headerU;
        this.serverApi = retrofitBuilder.build().create(ServerApi.class);
        this.empty = useEmpty;
        if (!useEmpty) {
            this.serviceAutoRegistryScheduledThreadPoolExecutor.scheduleWithFixedDelay(() -> autoRegistry(this), 1, 20, TimeUnit.SECONDS);
            this.serviceInfoCacheRefreshScheduledThreadPoolExecutor.scheduleWithFixedDelay(() -> autoRefreshCache(this), 1, 5, TimeUnit.SECONDS);
        } else {
            //触发一次下线，防止server端还有缓存
            try {
                offLine();
            } catch (Exception e) {
                log.warn("empty offLine: " + e.getMessage());
            }
        }
    }

    public boolean isEmpty() {
        return empty;
    }

    private void autoRefreshCache(ClientManage clientManage) {
        try {
            //定时刷新缓存
            Map<String, List<ServiceInfo>> serviceInfoCacheMap1 = clientManage.serviceInfoCacheMap;
            for (String snamecache : serviceInfoCacheMap1.keySet()) {
                try {
                    retrofit2.Response<JsonResult<List<ServiceInfo>>> execute = clientManage.serverApi.findCanUse(new HashMap<>(), new FindBody(snamecache)).execute();
                    if (execute.isSuccessful()) {
                        if (execute.body().getData() != null && !execute.body().getData().isEmpty()) {
                            //服务端空数据就不刷新缓存，可能缓存还能用
                            serviceInfoCacheMap1.put(snamecache, execute.body().getData());
                        }
                    } else {
                        log.error("autoRefreshCache findCanUse failed：{}", snamecache);
                    }
                } catch (IOException e) {
                    log.error("autoRefreshCache findCanUse failed IOException：{}", snamecache);
                }
            }
        } catch (Throwable ex) {
            log.error(ex.getMessage(), ex);
        }
    }

    private void autoRegistry(ClientManage clientManage) {
        try {
            if (clientManage != null) {
                RegistryBody requestBody = new RegistryBody();
                requestBody.setName(clientManage.serviceName);
                requestBody.setSchema(clientManage.schema);
                requestBody.setIp(clientManage.ip);
                requestBody.setPort(clientManage.port);
                try {
                    retrofit2.Response<JsonResult<String>> execute = clientManage.serverApi.registry(new HashMap<>(), requestBody).execute();
                    if (!execute.isSuccessful()) {
                        log.error("registry failed：{}", clientManage.serviceName);
                    }
                } catch (IOException ex) {
                    log.error("registry failed exception：{}", clientManage.serviceName);
                    log.error(ex.getMessage(), ex);
                }
            }
        } catch (Throwable ex) {
            log.error(ex.getMessage(), ex);
        }
    }

    public List<ServiceInfo> findCanUse(String sname) {
        List<ServiceInfo> canUseServiceList = this.serviceInfoCacheMap.getOrDefault(sname, null);
        if (canUseServiceList != null) {
            //优先缓存 即使注册中心偶尔挂掉也没事，缓存永远存在
            return canUseServiceList;
        } else {
            try {
                retrofit2.Response<JsonResult<List<ServiceInfo>>> execute = this.serverApi.findCanUse(new HashMap<>(), new FindBody(sname)).execute();
                if (execute.isSuccessful()) {
                    List<ServiceInfo> serviceInfoList = execute.body().getData();
                    //触发缓存更新
                    this.serviceInfoCacheMap.put(sname, serviceInfoList);

                    return serviceInfoList;
                } else {
                    log.error("findCanUse failed：{}", sname);
                    throw new RuntimeException(execute.message());
                }
            } catch (IOException e) {
                log.error("findCanUse failed IOException：{}", sname);
                throw new RuntimeException(e);
            }
        }
    }

    public void offLine() {
        try {
            retrofit2.Response<JsonResult<String>> execute = this.serverApi.offline(new HashMap<>(), new OffLineBody(this.serviceName, this.ip, this.port)).execute();
            if (execute.isSuccessful()) {
                //主动下线
            } else {
                log.error("offLine failed：{} {} {}", this.serviceName, this.ip, this.port);
                throw new RuntimeException(execute.message());
            }
        } catch (IOException e) {
            log.error("offLine failed IOException：{} {} {}", this.serviceName, this.ip, this.port);
            throw new RuntimeException(e);
        }
    }
}
